home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Tech Arsenal 1
/
Tech Arsenal (Arsenal Computer).ISO
/
tek-13
/
xvisrc.zip
/
CMDLINE.C
< prev
next >
Wrap
C/C++ Source or Header
|
1992-07-28
|
27KB
|
1,256 lines
/* Copyright (c) 1990,1991,1992 Chris and John Downey */
#ifndef lint
static char *sccsid = "@(#)cmdline.c 2.2 (Chris & John Downey) 8/6/92";
#endif
/***
* program name:
xvi
* function:
PD version of UNIX "vi" editor, with extensions.
* module name:
cmdline.c
* module function:
Command-line handling (i.e. :/? commands) - most
of the actual command functions are in ex_cmds.c.
* history:
STEVIE - ST Editor for VI Enthusiasts, Version 3.10
Originally by Tim Thompson (twitch!tjt)
Extensive modifications by Tony Andrews (onecom!wldrdg!tony)
Heavily modified by Chris & John Downey
***/
#include "xvi.h"
#ifdef MEGAMAX
overlay "cmdline"
#endif
/*
* The next two variables contain the bounds of any range
* given in a command. If no range was given, both will be NULL.
* If only a single line was given, u_line will be NULL.
* The a_line variable is used for those commands which take
* a third line specifier after the command, e.g. "move", "copy".
*/
static Line *l_line, *u_line;
static Line *a_line;
/*
* Definitions for all ex commands.
*/
#define EX_ENOTFOUND -1 /* command not found */
#define EX_EAMBIGUOUS -2 /* could be more than one */
#define EX_ECANTFORCE -3 /* ! given where not appropriate */
#define EX_EBADARGS -4 /* inappropriate args given */
#define EX_NOCMD 1
#define EX_SHCMD 2
#define EX_UNUSED 3 /* unused */
#define EX_AMPERSAND 4
#define EX_EXBUFFER 5
#define EX_LSHIFT 6
#define EX_EQUALS 7
#define EX_RSHIFT 8
#define EX_COMMENT 9
#define EX_ABBREVIATE 10
#define EX_APPEND 11
#define EX_ARGS 12
#define EX_BUFFER 13
#define EX_CHDIR 14
#define EX_CHANGE 15
#define EX_CLOSE 16
#define EX_COMPARE 17
#define EX_COPY 18
#define EX_DELETE 19
#define EX_ECHO 20
#define EX_EDIT 21
#define EX_EX 22
#define EX_FILE 23
#define EX_GLOBAL 24
#define EX_HELP 25
#define EX_INSERT 26
#define EX_JOIN 27
#define EX_K 28
#define EX_LIST 29
#define EX_MAP 30
#define EX_MARK 31
#define EX_MOVE 32
#define EX_NEXT 33
#define EX_NUMBER 34
#define EX_OPEN 35
#define EX_PRESERVE 36
#define EX_PRINT 37
#define EX_PUT 38
#define EX_QUIT 39
#define EX_READ 40
#define EX_RECOVER 41
#define EX_REWIND 42
#define EX_SET 43
#define EX_SHELL 44
#define EX_SOURCE 45
#define EX_SPLIT 46
#define EX_SUSPEND 47
#define EX_SUBSTITUTE 48
#define EX_TAG 49
#define EX_UNABBREV 50
#define EX_UNDO 51
#define EX_UNMAP 52
#define EX_V 53
#define EX_VERSION 54
#define EX_VISUAL 55
#define EX_WN 56
#define EX_WQ 57
#define EX_WRITE 58
#define EX_XIT 59
#define EX_YANK 60
#define EX_Z 61
#define EX_GOTO 62
#define EX_TILDE 63
/*
* Table of all ex commands, and whether they take an '!'.
*
* Note that this table is in strict order, sorted on
* the ASCII value of the first character of the command.
*
* The priority field is necessary to resolve clashes in
* the first one or two characters; so each group of commands
* beginning with the same letter should have at least one
* priority 1, so that there is a sensible default.
*
* Commands with argument type ec_rest need no delimiters;
* they need only be matched. This is really only used for
* single-character commands like !, " and &.
*/
static struct ecmd {
char *ec_name;
short ec_command;
short ec_priority;
unsigned ec_flags;
/*
* Flags: EXCLAM means can use !, FILEXP means do filename
* expansion, INTEXP means do % and # expansion. EXPALL means
* do INTEXP and FILEXP (they are done in that order).
*
* EC_RANGE0 means that the range specifier (if any)
* may include line 0.
*/
# define EC_EXCLAM 0x1
# define EC_FILEXP 0x2
# define EC_INTEXP 0x4
# define EC_EXPALL EC_FILEXP|EC_INTEXP
# define EC_RANGE0 0x8
enum {
ec_none, /* no arguments after command */
ec_strings, /* whitespace-separated strings */
ec_1string, /* like ec_strings but only one */
ec_line, /* line number or target argument */
ec_rest, /* rest of line passed entirely */
ec_nonalnum, /* non-alphanumeric delimiter */
ec_1lower /* single lower-case letter */
} ec_arg_type;
} cmdtable[] = {
/* name command priority exclam */
/*
* The zero-length string is used for the :linenumber command.
*/
"", EX_NOCMD, 1, EC_RANGE0, ec_none,
"!", EX_SHCMD, 0, EC_INTEXP, ec_rest,
"#", EX_NUMBER, 0, 0, ec_none,
"&", EX_AMPERSAND, 0, 0, ec_rest,
"*", EX_EXBUFFER, 0, 0, ec_rest,
"<", EX_LSHIFT, 0, 0, ec_none,
"=", EX_EQUALS, 0, 0, ec_none,
">", EX_RSHIFT, 0, 0, ec_none,
"@", EX_EXBUFFER, 0, 0, ec_rest,
"\"", EX_COMMENT, 0, 0, ec_rest,
"abbreviate", EX_ABBREVIATE, 0, 0, ec_strings,
"append", EX_APPEND, 1, 0, ec_none,
"args", EX_ARGS, 0, 0, ec_none,
"buffer", EX_BUFFER, 0, EC_EXPALL, ec_1string,
"cd", EX_CHDIR, 1, EC_EXPALL, ec_1string,
"change", EX_CHANGE, 2, 0, ec_none,
"chdir", EX_CHDIR, 1, EC_EXPALL, ec_1string,
"close", EX_CLOSE, 1, EC_EXCLAM, ec_none,
"compare", EX_COMPARE, 0, 0, ec_none,
"copy", EX_COPY, 1, 0, ec_line,
"delete", EX_DELETE, 0, 0, ec_none,
"echo", EX_ECHO, 0, EC_INTEXP, ec_strings,
"edit", EX_EDIT, 1, EC_EXCLAM|EC_EXPALL, ec_1string,
"ex", EX_EX, 0, EC_EXPALL, ec_1string,
"file", EX_FILE, 0, EC_EXPALL, ec_1string,
"global", EX_GLOBAL, 0, EC_EXCLAM, ec_nonalnum,
"help", EX_HELP, 0, 0, ec_none,
"insert", EX_INSERT, 0, 0, ec_none,
"join", EX_JOIN, 0, 0, ec_none,
"k", EX_K, 0, 0, ec_1lower,
"list", EX_LIST, 0, 0, ec_none,
"map", EX_MAP, 0, EC_EXCLAM, ec_strings,
"mark", EX_MARK, 0, 0, ec_1lower,
"move", EX_MOVE, 1, 0, ec_line,
"next", EX_NEXT, 1, EC_EXCLAM|EC_EXPALL, ec_strings,
"number", EX_NUMBER, 0, 0, ec_none,
"open", EX_OPEN, 0, 0, ec_none,
"preserve", EX_PRESERVE, 0, 0, ec_none,
"print", EX_PRINT, 1, 0, ec_none,
"put", EX_PUT, 0, EC_RANGE0, ec_none,
"quit", EX_QUIT, 0, EC_EXCLAM, ec_none,
"read", EX_READ, 1, EC_EXPALL|EC_RANGE0, ec_1string,
"recover", EX_RECOVER, 0, 0, ec_none,
"rewind", EX_REWIND, 0, EC_EXCLAM, ec_none,
"set", EX_SET, 0, 0, ec_strings,
"shell", EX_SHELL, 0, 0, ec_none,
"source", EX_SOURCE, 0, EC_EXPALL, ec_1string,
"split", EX_SPLIT, 0, 0, ec_none,
"stop", EX_SUSPEND, 0, 0, ec_none,
"substitute", EX_SUBSTITUTE, 1, 0, ec_nonalnum,
"suspend", EX_SUSPEND, 0, 0, ec_none,
"t", EX_COPY, 1, 0, ec_line,
"tag", EX_TAG, 0, EC_EXCLAM, ec_1string,
"unabbreviate", EX_UNABBREV, 0, 0, ec_strings,
"undo", EX_UNDO, 1, 0, ec_none,
"unmap", EX_UNMAP, 0, EC_EXCLAM, ec_strings,
"v", EX_V, 1, 0, ec_nonalnum,
"version", EX_VERSION, 0, 0, ec_none,
"visual", EX_VISUAL, 0, EC_EXCLAM|EC_EXPALL, ec_1string,
"wn", EX_WN, 0, EC_EXCLAM, ec_none,
"wq", EX_WQ, 0, EC_EXCLAM|EC_EXPALL, ec_1string,
"write", EX_WRITE, 1, EC_EXCLAM|EC_EXPALL, ec_1string,
"xit", EX_XIT, 0, 0, ec_none,
"yank", EX_YANK, 0, 0, ec_none,
"z", EX_Z, 0, 0, ec_none,
"|", EX_GOTO, 0, 0, ec_none,
"~", EX_TILDE, 0, 0, ec_rest,
NULL, 0, 0, 0, ec_none,
};
/*
* Internal routine declarations.
*/
static int decode_command P((char **, bool_t *, struct ecmd **));
static bool_t get_line P((char **, Line **));
static bool_t get_range P((char **));
static void badcmd P((bool_t, char *));
static char *show_line P((void));
static char *expand_percents P((char *));
/*
* These are used for display mode.
*/
static Line *curline;
static Line *lastline;
static bool_t do_line_numbers;
/*
* Macro to skip over whitespace during command line interpretation.
*/
#define skipblanks(p) { while (*(p) != '\0' && is_space(*(p))) (p)++; }
/*
* do_colon() - process a ':' command.
*
* The cmdline argument points to a complete command line to be processed
* (this does not include the ':' itself).
*/
void
do_colon(cmdline, interactive)
char *cmdline; /* optional command string */
bool_t interactive; /* true if reading from tty */
{
char *arg; /* ptr to string arg(s) */
int argc = 0; /* arg count for ec_strings */
char **argv = NULL; /* arg vector for ec_strings */
bool_t exclam; /* true if ! was given */
int command; /* which command it is */
struct ecmd *ecp; /* ptr to command entry */
unsigned savecho; /* previous value of echo */
/*
* Clear the range variables.
*/
l_line = NULL;
u_line = NULL;
/*
* Parse a range, if present (and update the cmdline pointer).
*/
if (!get_range(&cmdline)) {
return;
}
/*
* Decode the command.
*/
skipblanks(cmdline);
command = decode_command(&cmdline, &exclam, &ecp);
if (command > 0) {
/*
* Check that the range specified,
* if any, is legal for the command.
*/
if (!(ecp->ec_flags & EC_RANGE0)) {
if (l_line == curbuf->b_line0 || u_line == curbuf->b_line0) {
show_error(curwin,
"Specification of line 0 not allowed");
return;
}
}
switch (ecp->ec_arg_type) {
case ec_none:
if (*cmdline != '\0' &&
(*cmdline != '!' || !(ecp->ec_flags & EC_EXCLAM))) {
command = EX_EBADARGS;
}
break;
case ec_line:
a_line = NULL;
skipblanks(cmdline);
if (!get_line(&cmdline, &a_line) || a_line == NULL) {
command = EX_EBADARGS;
}
break;
case ec_1lower:
/*
* One lower-case letter.
*/
skipblanks(cmdline);
if (!is_lower(cmdline[0]) || cmdline[1] != '\0') {
command = EX_EBADARGS;
} else {
arg = cmdline;
}
break;
case ec_nonalnum:
case ec_rest:
case ec_strings:
case ec_1string:
arg = cmdline;
if (ecp->ec_arg_type == ec_strings ||
ecp->ec_arg_type == ec_1string) {
if (*arg == '\0') {
/*
* No args.
*/
arg = NULL;
} else {
/*
* Null-terminate the command and skip
* whitespace to arg or end of line.
*/
*arg++ = '\0';
skipblanks(arg);
/*
* There was trailing whitespace,
* but no args.
*/
if (*arg == '\0') {
arg = NULL;
}
}
} else if (ecp->ec_arg_type == ec_nonalnum && exclam) {
/*
* We don't normally touch the arguments for
* this type, but we have to null-terminate
* the '!' at least.
*/
*arg++ = '\0';
}
if (arg != NULL) {
/*
* Perform expansions on the argument string.
*/
if (ecp->ec_flags & EC_INTEXP) {
arg = expand_percents(arg);
}
if (ecp->ec_flags & EC_FILEXP) {
arg = fexpand(arg);
}
if (ecp->ec_arg_type == ec_strings) {
makeargv(arg, &argc, &argv, " \t");
}
}
}
}
savecho = echo;
/*
* Now do the command.
*/
switch (command) {
case EX_SHCMD:
/*
* If a line range was specified, this must be a pipe command.
* Otherwise, it's just a simple shell command.
*/
if (l_line != NULL) {
specify_pipe_range(curwin, l_line, u_line);
do_pipe(curwin, arg);
} else {
do_shcmd(curwin, arg);
}
break;
case EX_ARGS:
do_args(curwin);
break;
case EX_BUFFER:
if (arg != NULL)
echo &= ~(e_SCROLL | e_REPORT | e_SHOWINFO);
(void) do_buffer(curwin, arg);
move_window_to_cursor(curwin);
update_window(curwin);
break;
case EX_CHDIR:
{
char *error;
if ((error = do_chdir(arg)) != NULL) {
badcmd(interactive, error);
} else if (interactive) {
char *dirp;
if ((dirp = alloc(MAXPATHLEN + 2)) != NULL &&
getcwd(dirp, MAXPATHLEN + 2) != NULL) {
show_message(curwin, "%s", dirp);
}
if (dirp) {
free(dirp);
}
}
break;
}
case EX_CLOSE:
do_close_window(curwin, exclam);
break;
case EX_COMMENT: /* This one is easy ... */
break;
case EX_COMPARE:
do_compare();
break;
case EX_COPY:
do_cdmy('c', l_line, u_line, a_line);
break;
case EX_DELETE:
do_cdmy('d', l_line, u_line, (Line *) NULL);
break;
case EX_ECHO: /* echo arguments on command line */
{
int i;
flexclear(&curwin->w_statusline);
for (i = 0; i < argc; i++) {
if (!lformat(&curwin->w_statusline, "%s ", argv[i])
|| flexlen(&curwin->w_statusline) >= curwin->w_ncols) {
break;
}
}
update_sline(curwin);
break;
}
case EX_EDIT:
case EX_VISUAL: /* treat :vi as :edit */
echo &= ~(e_SCROLL | e_REPORT | e_SHOWINFO);
(void) do_edit(curwin, exclam, arg);
move_window_to_cursor(curwin);
update_buffer(curbuf);
#if 0
show_file_info(curwin);
#endif
break;
case EX_FILE:
if (arg != NULL) {
if (curbuf->b_filename != NULL)
free(curbuf->b_filename);
curbuf->b_filename = strsave(arg);
if (curbuf->b_tempfname != NULL) {
free(curbuf->b_tempfname);
curbuf->b_tempfname = NULL;
}
}
show_file_info(curwin);
break;
case EX_GLOBAL:
do_global(curwin, l_line, u_line, arg, !exclam);
break;
case EX_HELP:
do_help(curwin);
break;
case EX_MAP:
xvi_map(argc, argv, exclam, interactive);
break;
case EX_UNMAP:
xvi_unmap(argc, argv, exclam, interactive);
break;
case EX_MARK:
case EX_K:
{
Posn pos;
pos.p_index = 0;
if (l_line == NULL) {
pos.p_line = curwin->w_cursor->p_line;
} else {
pos.p_line = l_line;
}
(void) setmark(arg[0], curbuf, &pos);
break;
}
case EX_MOVE:
do_cdmy('m', l_line, u_line, a_line);
break;
case EX_NEXT:
/*
* do_next() handles turning off the appropriate bits
* in echo, & also calls move_window_to_cursor() &
* update_buffer() as required, so we don't have to
* do any of that here.
*/
do_next(curwin, argc, argv, exclam);
break;
case EX_PRESERVE:
if (do_preserve())
show_file_info(curwin);
break;
case EX_LIST:
case EX_PRINT:
case EX_NUMBER:
if (l_line == NULL) {
curline = curwin->w_cursor->p_line;
lastline = curline->l_next;
} else if (u_line == NULL) {
curline = l_line;
lastline = l_line->l_next;
} else {
curline = l_line;
lastline = u_line->l_next;
}
do_line_numbers = (Pb(P_number) || command == EX_NUMBER);
disp_init(curwin, show_line, (int) curwin->w_ncols,
command == EX_LIST || Pb(P_list));
break;
case EX_PUT:
{
Posn where;
if (l_line != NULL) {
where.p_index = 0;
where.p_line = l_line;
} else {
where.p_index = curwin->w_cursor->p_index;
where.p_line = curwin->w_cursor->p_line;
}
do_put(curwin, &where, FORWARD, '@');
break;
}
case EX_QUIT:
do_quit(curwin, exclam);
break;
case EX_REWIND:
do_rewind(curwin, exclam);
break;
case EX_READ:
if (arg == NULL) {
badcmd(interactive, "Unrecognized command");
break;
}
do_read(curwin, arg, (l_line != NULL) ? l_line :
curwin->w_cursor->p_line);
break;
case EX_SET:
do_set(curwin, argc, argv, interactive);
break;
case EX_SHELL:
do_shell(curwin);
break;
case EX_SOURCE:
if (arg == NULL) {
badcmd(interactive, "Missing filename");
} else if (do_source(interactive, arg) && interactive) {
show_file_info(curwin);
}
break;
case EX_SPLIT:
/*
* "split".
*/
do_split_window(curwin);
break;
case EX_SUBSTITUTE:
case EX_AMPERSAND:
case EX_TILDE:
{
long nsubs;
register long (*func) P((Xviwin *, Line *, Line *, char *));
switch (command) {
case EX_SUBSTITUTE:
func = do_substitute;
break;
case EX_AMPERSAND:
func = do_ampersand;
break;
case EX_TILDE:
func = do_tilde;
}
nsubs = (*func)(curwin, l_line, u_line, arg);
update_buffer(curbuf);
cursupdate(curwin);
begin_line(curwin, TRUE);
if (nsubs >= Pn(P_report)) {
show_message(curwin, "%ld substitution%c",
nsubs,
(nsubs > 1) ? 's' : ' ');
}
break;
}
case EX_SUSPEND:
do_suspend(curwin);
break;
case EX_TAG:
(void) do_tag(curwin, arg, exclam, TRUE, TRUE);
break;
case EX_V:
do_global(curwin, l_line, u_line, arg, FALSE);
break;
case EX_VERSION:
show_message(curwin, Version);
break;
case EX_WN:
/*
* This is not a standard "vi" command, but it
* is provided in PC vi, and it's quite useful.
*/
if (do_write(curwin, (char *) NULL, (Line *) NULL, (Line *) NULL,
exclam)) {
/*
* See comment for EX_NEXT (above).
*/
do_next(curwin, 0, argv, exclam);
#if 0
move_window_to_cursor(curwin);
update_buffer(curbuf);
#endif
}
break;
case EX_WQ:
do_wq(curwin, arg, exclam);
break;
case EX_WRITE:
(void) do_write(curwin, arg, l_line, u_line, exclam);
break;
case EX_XIT:
do_xit(curwin);
break;
case EX_YANK:
do_cdmy('y', l_line, u_line, (Line *) NULL);
break;
case EX_EXBUFFER:
yp_stuff_input(curwin, arg[0], FALSE);
break;
case EX_EQUALS:
do_equals(curwin, l_line);
break;
case EX_LSHIFT:
case EX_RSHIFT:
if (l_line == NULL) {
l_line = curwin->w_cursor->p_line;
}
if (u_line == NULL) {
u_line = l_line;
}
tabinout((command == EX_LSHIFT) ? '<' : '>', l_line, u_line);
begin_line(curwin, TRUE);
update_buffer(curbuf);
break;
case EX_NOCMD:
/*
* If we got a line, but no command, then go to the line.
*/
if (l_line != NULL) {
if (l_line == curbuf->b_line0) {
l_line = l_line->l_next;
}
move_cursor(curwin, l_line, 0);
begin_line(curwin, TRUE);
}
break;
case EX_ENOTFOUND:
badcmd(interactive, "Unrecognized command");
break;
case EX_EAMBIGUOUS:
badcmd(interactive, "Ambiguous command");
break;
case EX_ECANTFORCE:
badcmd(interactive, "Inappropriate use of !");
break;
case EX_EBADARGS:
badcmd(interactive, "Inappropriate arguments given");
break;
default:
/*
* Decoded successfully, but unknown to us. Whoops!
*/
badcmd(interactive, "Internal error - unimplemented command.");
break;
case EX_ABBREVIATE:
case EX_APPEND:
case EX_CHANGE:
case EX_EX:
case EX_GOTO:
case EX_INSERT:
case EX_JOIN:
case EX_OPEN:
case EX_RECOVER:
case EX_UNABBREV:
case EX_UNDO:
case EX_Z:
badcmd(interactive, "Unimplemented command.");
break;
}
echo = savecho;
if (argc > 0 && argv != NULL) {
free((char *) argv);
}
}
/*
* Find the correct command for the given string, and return it.
*
* Updates string pointer to point to 1st char after command.
*
* Updates boolean pointed to by forcep according
* to whether an '!' was given after the command;
* if an '!' is given for a command which can't take it,
* this is an error, and EX_ECANTFORCE is returned.
* For unknown commands, EX_ENOTFOUND is returned.
* For ambiguous commands, EX_EAMBIGUOUS is returned.
*
* Also updates *cmdp to point at entry in command table.
*/
static int
decode_command(str, forcep, cmdp)
char **str;
bool_t *forcep;
struct ecmd **cmdp;
{
struct ecmd *cmdptr;
struct ecmd *last_match = NULL;
bool_t last_exclam = FALSE;
int nmatches = 0;
char *last_delimiter = *str;
for (cmdptr = cmdtable; cmdptr->ec_name != NULL; cmdptr++) {
char *name = cmdptr->ec_name;
char *strp;
bool_t matched;
strp = *str;
while (*strp == *name && *strp != '\0') {
strp++;
name++;
}
matched = (
/*
* we've got a full match, or ...
*/
*strp == '\0'
||
/*
* ... a whitespace delimiter, or ...
*/
is_space(*strp)
||
(
/*
* ... at least one character has been
* matched, and ...
*/
name > cmdptr->ec_name
&&
(
(
/*
* ... this command can accept a
* '!' qualifier, and we've found
* one ...
*/
(cmdptr->ec_flags & EC_EXCLAM)
&&
*strp == '!'
)
||
(
/*
* ... or it can take a
* non-alphanumeric delimiter
* (like '/') ...
*/
cmdptr->ec_arg_type == ec_nonalnum
&&
!is_alnum(*strp)
)
||
(
/*
* ... or it doesn't need any
* delimiter ...
*/
cmdptr->ec_arg_type == ec_rest
)
||
(
/*
* ... or we've got a full match,
* & the command expects a single
* lower-case letter as an
* argument, and we've got one ...
*/
cmdptr->ec_arg_type == ec_1lower
&&
*name == '\0'
&&
is_lower(*strp)
)
||
(
/*
* ... or we've got a partial match,
* and the command expects a line
* specifier as an argument, and the
* next character is not alphabetic.
*/
cmdptr->ec_arg_type == ec_line
&&
!is_alpha(*strp)
)
)
)
);
if (!matched)
continue;
if (last_match == NULL ||
(last_match != NULL &&
last_match->ec_priority < cmdptr->ec_priority)) {
/*
* No previous match, or this one is better.
*/
last_match = cmdptr;
last_exclam = (*strp == '!');
last_delimiter = strp;
nmatches = 1;
/*
* If this is a complete match, we don't
* need to search the rest of the table.
*/
if (*name == '\0')
break;
} else if (last_match != NULL &&
last_match->ec_priority == cmdptr->ec_priority) {
/*
* Another match with the same priority - remember it.
*/
nmatches++;
}
}
/*
* For us to have found a good match, there must have been
* exactly one match at a certain priority, and if an '!'
* was used, it must be allowed by that match.
*/
if (last_match == NULL) {
return(EX_ENOTFOUND);
} else if (nmatches != 1) {
return(EX_EAMBIGUOUS);
} else if (last_exclam && ! (last_match->ec_flags & EC_EXCLAM)) {
return(EX_ECANTFORCE);
} else {
*forcep = last_exclam;
*str = last_delimiter;
*cmdp = last_match;
return(last_match->ec_command);
}
}
/*
* get_range - parse a range specifier
*
* Ranges are of the form
*
* ADDRESS1,ADDRESS2
* ADDRESS (equivalent to "ADDRESS,ADDRESS")
* ADDRESS, (equivalent to "ADDRESS,.")
* ,ADDRESS (equivalent to ".,ADDRESS")
* , (equivalent to ".")
* % (equivalent to "1,$")
*
* where ADDRESS is
*
* /regular expression/ [INCREMENT]
* ?regular expression? [INCREMENT]
* $ [INCREMENT]
* 'x [INCREMENT] (where x denotes a currently defined mark)
* . [INCREMENT]
* INCREMENT (equivalent to . INCREMENT)
* number [INCREMENT]
*
* and INCREMENT is
*
* + number
* - number
* + (equivalent to +1)
* - (equivalent to -1)
* ++ (equivalent to +2)
* -- (equivalent to -2)
*
* etc.
*
* The pointer *cpp is updated to point to the first character following
* the range spec. If an initial address is found, but no second, the
* upper bound is equal to the lower, except if it is followed by ','.
*
* Return FALSE if an error occurred, otherwise TRUE.
*/
static bool_t
get_range(cpp)
register char **cpp;
{
register char *cp;
char sepchar;
#define skipp { cp = *cpp; skipblanks(cp); *cpp = cp; }
skipp;
if (*cp == '%') {
/*
* "%" is the same as "1,$".
*/
l_line = curbuf->b_file;
u_line = curbuf->b_lastline->l_prev;
++*cpp;
return TRUE;
}
if (!get_line(cpp, &l_line)) {
return FALSE;
}
skipp;
sepchar = *cp;
if (sepchar != ',' && sepchar != ';') {
u_line = l_line;
return TRUE;
}
++*cpp;
if (l_line == NULL) {
/*
* So far, we've got ",".
*
* ",address" is equivalent to ".,address".
*/
l_line = curwin->w_cursor->p_line;
}
if (sepchar == ';') {
move_cursor(curwin, (l_line != curbuf->b_line0) ?
l_line : l_line->l_next, 0);
}
skipp;
if (!get_line(cpp, &u_line)) {
return FALSE;
}
if (u_line == NULL) {
/*
* "address," is equivalent to "address,.".
*/
u_line = curwin->w_cursor->p_line;
}
/*
* Check for reverse ordering.
*/
if (earlier(u_line, l_line)) {
Line *tmp;
tmp = l_line;
l_line = u_line;
u_line = tmp;
}
skipp;
return TRUE;
}
static bool_t
get_line(cpp, lpp)
char **cpp;
Line **lpp;
{
Line *pos;
char *cp;
char c;
long lnum;
cp = *cpp;
/*
* Determine the basic form, if present.
*/
switch (c = *cp++) {
case '/':
case '?':
pos = linesearch(curwin, (c == '/') ? FORWARD : BACKWARD,
&cp);
if (pos == NULL) {
return FALSE;
}
break;
case '$':
pos = curbuf->b_lastline->l_prev;
break;
/*
* "+n" is equivalent to ".+n".
*/
case '+':
case '-':
cp--;
/* fall through ... */
case '.':
pos = curwin->w_cursor->p_line;
break;
case '\'': {
Posn *lp;
lp = getmark(*cp++, curbuf);
if (lp == NULL) {
show_error(curwin, "Unknown mark");
return FALSE;
}
pos = lp->p_line;
break;
}
case '0': case '1': case '2': case '3': case '4':
case '5': case '6': case '7': case '8': case '9':
for (lnum = c - '0'; is_digit(*cp); cp++)
lnum = lnum * 10 + *cp - '0';
if (lnum == 0) {
pos = curbuf->b_line0;
} else {
pos = gotoline(curbuf, lnum);
}
break;
default:
return TRUE;
}
skipblanks(cp);
if (*cp == '-' || *cp == '+') {
char dirchar = *cp++;
skipblanks(cp);
if (is_digit(*cp)) {
for (lnum = 0; is_digit(*cp); cp++) {
lnum = lnum * 10 + *cp - '0';
}
} else {
for (lnum = 1; *cp == dirchar; cp++) {
lnum++;
}
}
if (dirchar == '-')
lnum = -lnum;
pos = gotoline(curbuf, lineno(curbuf, pos) + lnum);
}
*cpp = cp;
*lpp = pos;
return TRUE;
}
/*
* This routine is called for ambiguous, unknown,
* badly defined or unimplemented commands.
*/
static void
badcmd(interactive, str)
bool_t interactive;
char *str;
{
if (interactive) {
show_error(curwin, str);
}
}
static char *
show_line()
{
Line *lp;
if (curline == lastline) {
return(NULL);
}
lp = curline;
curline = curline->l_next;
if (do_line_numbers) {
static Flexbuf nu_line;
flexclear(&nu_line);
(void) lformat(&nu_line, NUM_FMT, lineno(curbuf, lp));
(void) lformat(&nu_line, "%s", lp->l_text);
return flexgetstr(&nu_line);
} else {
return(lp->l_text);
}
}
static char *
expand_percents(str)
char *str;
{
static Flexbuf newstr;
register char *from;
register bool_t escaped;
if (strpbrk(str, "%#") == (char *) NULL) {
return str;
}
flexclear(&newstr);
escaped = FALSE;
for (from = str; *from != '\0'; from++) {
if (!escaped) {
if (*from == '%' && curbuf->b_filename != NULL) {
(void) lformat(&newstr, "%s", curbuf->b_filename);
} else if (*from == '#' && altfilename != NULL) {
(void) lformat(&newstr, "%s", altfilename);
} else if (*from == '\\') {
escaped = TRUE;
} else {
(void) flexaddch(&newstr, *from);
}
} else {
if (*from != '%' && *from != '#') {
(void) flexaddch(&newstr, '\\');
}
(void) flexaddch(&newstr, *from);
escaped = FALSE;
}
}
return flexgetstr(&newstr);
}